package com.test.utils;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

public class RSAHelper {

    private static final String RSA = "RSA";

    public static final String PUBLIC_KEY = "public_key";

    public static final String PRIVATE_KEY = "private_key";

    /**
     * 算法名/工作模式/填充模式
     */
    public static final String RSA_ECB_PKCS1Padding = "RSA/ECB/PKCS1Padding";

    /**
     * 签名算法
     */
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    /**
     * 密钥长度
     */
    private int secretKeyLength = 2048;

    /**
     * RSA最大加密明文大小
     */
    private int maxEncryptBlock;

    /**
     * RSA最大解密密文大小
     */
    private int maxDecryptBlock;

    public RSAHelper() {
        init(0);
    }

    /**
     * 初始化RSA加密密钥长度
     *
     * @param secretKeyLength
     */
    public void init(int secretKeyLength) {
        if (secretKeyLength > 0
                && secretKeyLength != this.secretKeyLength
                && secretKeyLength % 1024 == 0) {
            if (this.secretKeyLength != secretKeyLength) {
                this.secretKeyLength = secretKeyLength;
            }
        }
        this.maxDecryptBlock = this.secretKeyLength / 8;
        this.maxEncryptBlock = this.maxDecryptBlock / 8 - 11;
    }

    /**
     * 生成密钥对
     */
    public Map<String, Object> getKeyMap() throws GeneralSecurityException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA);
        keyPairGen.initialize(this.secretKeyLength);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>();
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**
     * 生成密钥对
     */
    public static Map<String, Object> getKeyMap(int keySize) throws GeneralSecurityException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA);
        keyPairGen.initialize(keySize);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>();
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**
     * 用私钥对信息生成数字签名
     *
     * @param data       已加密数据
     * @param privateKey 私钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public byte[] sign(byte[] data, String privateKey) throws GeneralSecurityException {
        byte[] keyBytes = Base64Utils.decode2Bytes(privateKey);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PrivateKey privateK = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateK);
        signature.update(data);
        return signature.sign();
    }

    /**
     * 校验数字签名
     *
     * @param data      已加密数据
     * @param publicKey 公钥(BASE64编码)
     * @param sign      数字签名
     * @return
     * @throws Exception
     */
    public boolean verify(byte[] data, String publicKey, byte[] sign) throws GeneralSecurityException {
        byte[] keyBytes = Base64Utils.decode2Bytes(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PublicKey publicK = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(publicK);
        signature.update(data);
        return signature.verify(sign);
    }

    /**
     * 私钥加密
     *
     * @param data       源数据
     * @param privateKey 私钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public byte[] encryptByPrivateKey(byte[] data, String privateKey) throws GeneralSecurityException, IOException {
        Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1Padding);
        cipher.init(Cipher.ENCRYPT_MODE, base64ToPrivateKey(privateKey));
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > this.maxEncryptBlock) {
                cache = cipher.doFinal(data, offSet, this.maxEncryptBlock);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * this.maxEncryptBlock;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

    /**
     * 私钥解密
     *
     * @param encryptedData 已加密数据
     * @param privateKey    私钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws GeneralSecurityException, IOException {
        Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1Padding);
        cipher.init(Cipher.DECRYPT_MODE, base64ToPrivateKey(privateKey));
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > this.maxDecryptBlock) {
                cache = cipher.doFinal(encryptedData, offSet, this.maxDecryptBlock);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * this.maxDecryptBlock;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * 公钥加密
     *
     * @param data      源数据
     * @param publicKey 公钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public byte[] encryptByPublicKey(byte[] data, String publicKey) throws GeneralSecurityException, IOException {
        // 对数据加密
        Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1Padding);
        cipher.init(Cipher.ENCRYPT_MODE, base64ToPublicKey(publicKey));
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > this.maxEncryptBlock) {
                cache = cipher.doFinal(data, offSet, this.maxEncryptBlock);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * this.maxEncryptBlock;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

    /**
     * 公钥解密
     *
     * @param encryptedData 已加密数据
     * @param publicKey     公钥(BASE64编码)
     * @return
     * @throws IOException
     * @throws Exception
     */
    public byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws GeneralSecurityException, IOException {
        Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1Padding);
        cipher.init(Cipher.DECRYPT_MODE, base64ToPublicKey(publicKey));
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > this.maxDecryptBlock) {
                cache = cipher.doFinal(encryptedData, offSet, this.maxDecryptBlock);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * this.maxDecryptBlock;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * base64字符串转公钥
     *
     * @param str
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static Key base64ToPublicKey(String str) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] keyBytes = Base64Utils.decode2Bytes(str);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        Key key = keyFactory.generatePublic(keySpec);
        return key;
    }

    /**
     * base64字符串转私钥
     *
     * @param str
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static Key base64ToPrivateKey(String str) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] keyBytes = Base64Utils.decode2Bytes(str);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        Key key = keyFactory.generatePrivate(keySpec);
        return key;
    }

    /**
     * 获取私钥字符串
     *
     * @param keyMap 密钥对
     * @return
     */
    public static String getPrivateKey(Map<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return Base64Utils.encode(key.getEncoded());
    }

    /**
     * 获取公钥字符串
     *
     * @param keyMap 密钥对
     * @return
     */
    public static String getPublicKey(Map<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return Base64Utils.encode(key.getEncoded());
    }

    /**
     * @param data
     * @return
     * @功能: 移除微软前导0
     * @作者: yangc
     * @创建日期: 2017年2月14日 上午11:37:21
     */
    private static byte[] removeMSZero(byte[] data) {
        byte[] b;
        if (data[0] == 0) {
            b = new byte[data.length - 1];
            System.arraycopy(data, 1, b, 0, data.length - 1);
        } else {
            b = data;
        }
        return b;
    }

    /**
     * 获取私钥(C#格式)
     *
     * @param keyMap 密钥对
     * @return
     */
    public static String getPrivateKey2CSharp(Map<String, Object> keyMap) throws GeneralSecurityException {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key.getEncoded());
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        RSAPrivateCrtKey privateK = (RSAPrivateCrtKey) keyFactory.generatePrivate(keySpec);

        StringBuilder sb = new StringBuilder();
        sb.append("<RSAKeyValue>");
        sb.append("<Modulus>" + Base64Utils.encode(removeMSZero(privateK.getModulus().toByteArray())) + "</Modulus>");
        sb.append("<Exponent>" + Base64Utils.encode(removeMSZero(privateK.getPublicExponent().toByteArray())) + "</Exponent>");
        sb.append("<P>" + Base64Utils.encode(removeMSZero(privateK.getPrimeP().toByteArray())) + "</P>");
        sb.append("<Q>" + Base64Utils.encode(removeMSZero(privateK.getPrimeQ().toByteArray())) + "</Q>");
        sb.append("<DP>" + Base64Utils.encode(removeMSZero(privateK.getPrimeExponentP().toByteArray())) + "</DP>");
        sb.append("<DQ>" + Base64Utils.encode(removeMSZero(privateK.getPrimeExponentQ().toByteArray())) + "</DQ>");
        sb.append("<InverseQ>" + Base64Utils.encode(removeMSZero(privateK.getCrtCoefficient().toByteArray())) + "</InverseQ>");
        sb.append("<D>" + Base64Utils.encode(removeMSZero(privateK.getPrivateExponent().toByteArray())) + "</D>");
        sb.append("</RSAKeyValue>");
        return sb.toString();
    }

    /**
     * 获取公钥(C#格式)
     *
     * @param keyMap 密钥对
     * @return
     */
    public static String getPublicKey2CSharp(Map<String, Object> keyMap) throws GeneralSecurityException {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key.getEncoded());
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        RSAPublicKey publicK = (RSAPublicKey) keyFactory.generatePublic(keySpec);

        StringBuilder sb = new StringBuilder();
        sb.append("<RSAKeyValue>");
        sb.append("<Modulus>" + Base64Utils.encode(removeMSZero(publicK.getModulus().toByteArray())) + "</Modulus>");
        sb.append("<Exponent>" + Base64Utils.encode(removeMSZero(publicK.getPublicExponent().toByteArray())) + "</Exponent>");
        sb.append("</RSAKeyValue>");
        return sb.toString();
    }

}
